/*
    http://bhepple.freeshell.org

    Copyright (C) 2010 Bob Hepple <bhepple@freeshell.org>

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; see the file COPYING.  If not, write to
    the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
    Boston, MA 02111-1307, USA.

*/

// FIXME: add tests for non-NULL end_location

/**
 * \file tbd_test.c unit tests for tbd.c
 *
 * To run from the source directory:
 *
 * libtool --mode=execute valgrind --leak-check=full --show-reachable=yes ./tests/tbd_test
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <tbd.h>
#include <assert.h>
#include "./tb_expect.h"

int errors = 0;

void *xcalloc (size_t nmemb, size_t size)
{
    void *retval = calloc(nmemb, size);

    /* Normally (ie as specified on GNU pages), xcalloc etc do not
     * return when the heap is exhausted. tbd does not make that
     * assumption and provides code for the heap exhaustion case.
     *
     * For example, a longjmp could be used here to take us to a
     * recovery routine ie a routine that has previously called
     * setjmp(). It could free some stuff (maybe a buffer alloc'd as a
     * safety reserve) and then either exit safely or try to continue,
     * perhaps with a warning.
     *
     * extern jmp_buf               NoMoreHeap; 
     * if (!retval) longjmp(NoMoreHeap, 1); */

    return(retval);
}

void *xmalloc (size_t size)
{
    void *retval = malloc(size);
    /* if (!retval) longjmp(NoMoreHeap, 1); */
    return(retval);
}

void *xrealloc(void *oldbuf, size_t size)
{
    void *retval = NULL;

    retval = realloc(oldbuf, size);

    /* if (!retval) longjmp(NoMoreHeap, 1); */
    return(retval);
}

void xfree(void *buf)
{
    free(buf);
}

void header(char *s, int line)
{
    static int last_error = 0;
    if (errors == last_error) {
        printf("ok\n");
    } else {
        last_error = errors;
    }

    printf("*******************************\nline %d: %s:\n", line, s);
}

#define HEADER(x) header(x, __LINE__)
#define PRINT_CLUES                                                     \
    printf("width = %d\n", width);                                      \
    printf("callbacks = (%p,%p,%p)\n", free_callback,                   \
           create_callback, copy_callback);                             \
    errors++;

#define CHECK_DLIST(a, s)                                               \
    for (item = tbd_get_first(a); item; item = tbd_get_next(item)) {    \
        bEXPECT(check_payload((test_payload)item->payload));            \
    }                                                                   \
    iEXPECT(tbd_get_len(a), s);

#define CHECK_ERRORS(x) if ( (errors += x) ) goto end;

#define TEST_STRING "Hello world"
#define SENTINEL1 0xdeadbeef
#define SENTINEL2 0xbeefdead
#define ORIG_SIZE (20*2) // needs to be even and >= 20
#define ORIG_WIDTH 20
#define NEW_SIZE 1000
#define COPY_OFFSET 4000

typedef struct test_payload_s {
    int sentinel1;
    char *text;
    int count;
    int sentinel2;
} *test_payload;

/* constant width payload */

/* callback frees anything pointed to in payload and returns payload */
static void *f_free_callback(void *pl_in, void *caller_data)
{
    test_payload pl = (test_payload) pl_in;

    if (pl && pl->text) xfree(pl->text);
    return(pl);
}

/* assumes tbd has done malloc - don't check so we get an abort on
 * first error! */
static void *f_create_callback(void *pl_in, void *caller_data)
{
    void *retval = NULL;
    test_payload pl = (test_payload) pl_in;
    int count = *(int *)caller_data;

    assert(pl_in != NULL);
    retval = pl_in;
    pl->sentinel1 = SENTINEL1;
    pl->text = strdup(TEST_STRING);
    pl->count = count++;
    *(int *)caller_data = count;
    pl->sentinel2 = SENTINEL2;
    // printf("f_create_callback: count=%d\n", count);
    return(retval);
}

/* assumes tbd has done malloc */
static void *f_copy_callback(void *dest,
                             void *source,
                             void *caller_data)
{
    test_payload dpl = NULL;
    test_payload spl = NULL;
    void *retval = NULL;

    assert(dest != NULL);

    dpl = (test_payload ) dest;
    spl = (test_payload ) source;

    retval = dest;
    dpl->sentinel1 = spl->sentinel1;
    dpl->text = strdup(spl->text);
    dpl->count = spl->count + COPY_OFFSET;
    dpl->sentinel2 = spl->sentinel2;

    return(retval);
}

/* variable length payloads */

/* callback does free and returns NULL */
static void *v_free_callback(void *pl_in,
                             void *caller_data)
{
    test_payload pl = (test_payload) pl_in;

    if (pl && pl->text) xfree(pl->text);
    if (pl_in) xfree(pl_in);
    return(NULL);
}

/* callback does malloc because tbd doesn't know the width */
static void *v_create_callback(void *pl_in,
                               void *caller_data)
{
    void *retval = NULL;
    test_payload pl;
    int count = *(int *)caller_data;

    assert(pl_in == NULL);
    pl = xmalloc(sizeof(struct test_payload_s));
    retval = pl;

    if (pl) {
        pl->sentinel1 = SENTINEL1;
        pl->text = strdup(TEST_STRING);
        pl->count = count++;
        *(int *)caller_data = count;
        pl->sentinel2 = SENTINEL2;
        // printf("v_create_callback: count=%d\n", count);
    }
    return(retval);
}

/* calls create_callback */
static void *v_copy_callback(void *dest,
                             void *source, 
                             void *caller_data)
{
    void *retval = NULL;

    assert(dest == NULL);
    if (!source) goto end;

    retval = v_create_callback(dest, caller_data);
    if (retval) {
        test_payload dpl;
        test_payload spl;

        dpl = (test_payload) retval;
        spl = (test_payload) source;
        if (dpl->text) xfree(dpl->text);
        dpl->sentinel1 = spl->sentinel1;
        dpl->text = strdup(spl->text);
        dpl->count = spl->count + 4000;
        dpl->sentinel2 = spl->sentinel2;
    }

end:
    return(retval);
}

int check_payload(test_payload pl)
{
    int retval = 0;

    if (!pl) {
        printf("\ncheck_payload: error: NULL payload\n");
        goto end;
    }
    if (pl->sentinel1 != SENTINEL1) {
        printf("\ncheck_payload: error: bad sentinel1 in payload\n");
        goto end;
    }
    if (!pl->text) {
        printf("\ncheck_payload: error: NULL text in payload\n");
        goto end;
    }
    if (strcmp(pl->text, TEST_STRING)) {
        printf("\ncheck_payload: error: bad text in payload\n");
        goto end;
    }

    if (pl->sentinel2 != SENTINEL2) {
        printf("\ncheck_payload: error: bad sentinel2 in payload\n");
        goto end;
    }
    // printf("Checking payload: %d '%s'\n", pl->count, pl->text);
    retval = 1;

end:
    return(retval);
}

tbd new_dlist(int                   size,
              int                   width, 
              tbd_free_callback_t   free_callback, 
              tbd_create_callback_t create_callback, 
              tbd_copy_callback_t   copy_callback,
              void *                data)
{
    tbd retval = NULL;
    int i;
    tbd_item item = NULL;

    // printf("new_dlist: size=%d, width=%d, free=%p, create=%p, copy=%p, data=%d\n",
    //        size, width, free_callback, create_callback, copy_callback, *(int *)data);
    
    retval = tbd_new(width, free_callback, create_callback, 
                     copy_callback, data);
    for (i = 0; i < size; i++) {
        item = tbd_append_new(&retval, item);
        assert(item);
        if (create_callback == NULL) {
            if (width == 0) {
                // variable width
                // we need to malloc and format the payload as the
                // callbacks have not done it
                item->payload = v_create_callback(NULL, data);
            } else { 
                // fixed width
                // we need to format the payload
                f_create_callback(item->payload, data);
            }
        }
    }
    return(retval);
}

void free_dlist(tbd *                 a,
                tbd_free_callback_t   free_callback, 
                tbd_create_callback_t create_callback, 
                tbd_copy_callback_t   copy_callback,
                void *                data)
{
    tbd_free(a);
}

int dlist_test_chop_items(int                   width, 
                          tbd_free_callback_t   free_callback, 
                          tbd_create_callback_t create_callback, 
                          tbd_copy_callback_t   copy_callback)
{
    int errors = 0;
    tbd a = NULL;
    tbd b = NULL;
    tbd_item item = NULL;
    int value;
    int count = 0;
    int user_data = 0;
    
    // printf("\ndlist_test_chop_items: width = %d callbacks = (%p,%p,%p)\n", 
    //        width, free_callback, create_callback, 
    //        copy_callback);
    
    HEADER("tbd_chop_items(NULL, NULL, 1)");
    user_data = 0;
    pEXPECT(a = new_dlist(ORIG_SIZE, width, free_callback, create_callback, 
                          copy_callback, &user_data));
    pEXPECT(b = tbd_chop_items(a, NULL, NULL, 1));
    iEXPECT(tbd_get_len(b), 1);
    iEXPECT(tbd_get_len(a), ORIG_SIZE - 1);
    pEXPECT(item = tbd_get_first(b));
    iEXPECT(check_payload(item->payload), 1);
    iEXPECT(((test_payload)item->payload)->count, 0);

    free_dlist(&a, free_callback, create_callback, 
               copy_callback, &count);

    free_dlist(&b, free_callback, create_callback, 
               copy_callback, &count);
    
    HEADER("tbd_chop_items(NULL, index(1), 1)");
    user_data = 0;
    pEXPECT(a = new_dlist(ORIG_SIZE, width, free_callback, create_callback, 
                          copy_callback, &user_data));
    pEXPECT(b = tbd_chop_items(a, NULL, tbd_get_index(a, 1), 1));
    iEXPECT(tbd_get_len(b), 1);
    iEXPECT(tbd_get_len(a), ORIG_SIZE - 1);
    pEXPECT(item = tbd_get_first(b));
    iEXPECT(check_payload(item->payload), 1);
    iEXPECT(((test_payload)item->payload)->count, 0);

    free_dlist(&a, free_callback, create_callback, 
               copy_callback, &count);

    free_dlist(&b, free_callback, create_callback, 
               copy_callback, &count);
    
    HEADER("tbd_chop_items(NULL, NULL, -1)");
    user_data = 0;
    pEXPECT(a = new_dlist(ORIG_SIZE, width, free_callback, create_callback, 
                          copy_callback, &user_data));
    pEXPECT(b = tbd_chop_items(a, NULL, NULL, -1));
    iEXPECT(tbd_get_len(b), 1);
    iEXPECT(tbd_get_len(a), ORIG_SIZE - 1);
    pEXPECT(item = tbd_get_first(b));
    iEXPECT(check_payload(item->payload), 1);
    iEXPECT(((test_payload)item->payload)->count, ORIG_SIZE - 1);

    free_dlist(&a, free_callback, create_callback, 
               copy_callback, &count);

    free_dlist(&b, free_callback, create_callback, 
               copy_callback, &count);
    
    HEADER("tbd_chop_items(NULL, NULL, 0)");
    user_data = 0;
    pEXPECT(a = new_dlist(ORIG_SIZE, width, free_callback, 
                          create_callback, copy_callback, &user_data));
    pEXPECT(b = tbd_chop_items(a, NULL, NULL, 0));
    iEXPECT(tbd_get_len(b), ORIG_SIZE);
    iEXPECT(tbd_get_len(a), 0);
    pEXPECT(item = tbd_get_first(b));
    iEXPECT(check_payload(item->payload), 1);
    iEXPECT(((test_payload)item->payload)->count, 0);

    free_dlist(&a, free_callback, create_callback, 
               copy_callback, &count);

    free_dlist(&b, free_callback, create_callback, 
               copy_callback, &count);
    
    HEADER("tbd_chop_items(first, NULL, 1)");
    user_data = 0;
    pEXPECT(a = new_dlist(ORIG_SIZE, width, free_callback, 
                          create_callback, copy_callback, &user_data));
    pEXPECT(b = tbd_chop_items(a, tbd_get_index(a, 1), NULL, 1));
    iEXPECT(tbd_get_len(b), 1);
    iEXPECT(tbd_get_len(a), ORIG_SIZE - 1);
    pEXPECT(item = tbd_get_first(b));
    iEXPECT(check_payload(item->payload), 1);
    iEXPECT(((test_payload)item->payload)->count, 1);

    free_dlist(&a, free_callback, create_callback, 
               copy_callback, &count);

    free_dlist(&b, free_callback, create_callback, 
               copy_callback, &count);
    
    HEADER("tbd_chop_items(first, second, 1)");
    user_data = 0;
    pEXPECT(a = new_dlist(ORIG_SIZE, width, free_callback, 
                          create_callback, copy_callback, &user_data));
    pEXPECT(b = tbd_chop_items(a, tbd_get_index(a, 1), 
                               tbd_get_index(a, 2), 1));
    iEXPECT(tbd_get_len(b), 1);
    iEXPECT(tbd_get_len(a), ORIG_SIZE - 1);
    pEXPECT(item = tbd_get_first(b));
    iEXPECT(check_payload(item->payload), 1);
    iEXPECT(((test_payload)item->payload)->count, 1);

    free_dlist(&a, free_callback, create_callback, 
               copy_callback, &count);

    free_dlist(&b, free_callback, create_callback, 
               copy_callback, &count);
end:
    return(errors);
}

int dlist_test_delete_items(int                   width, 
                            tbd_free_callback_t   free_callback, 
                            tbd_create_callback_t create_callback, 
                            tbd_copy_callback_t   copy_callback)
{
    int errors = 0;
    tbd a = NULL;
    tbd_item item = NULL;
    int value;
    int count = 0;
    int user_data = 0;
    
    // printf("\ndlist_test_delete_items: width = %d callbacks = (%p,%p,%p)\n", 
    //        width, free_callback, create_callback, 
    //        copy_callback);

    HEADER("tbd_new/free");
    pEXPECT(a = new_dlist(ORIG_SIZE, width, free_callback, create_callback, 
                          copy_callback, &user_data));
    CHECK_DLIST(a, ORIG_SIZE);
    
    HEADER("tbs_get_index");
    for (count = 0; count < ORIG_SIZE; count++) {
        pEXPECT(item = tbd_get_index(a, count));
        iEXPECT(check_payload(item->payload), 1);
        iEXPECT(((test_payload)item->payload)->count, count);
    }
    
    HEADER("tbd_delete_items(first, NULL, 1)");
    pEXPECT(item = tbd_get_first(a));
    iEXPECT(check_payload(item->payload), 1);
    pEXPECT(tbd_delete_items(a, item, NULL, 1));
    iEXPECT(tbd_get_len(a), ORIG_SIZE - 1);
    CHECK_DLIST(a, ORIG_SIZE - 1);
    
    HEADER("tbd_delete_items(first, second, 1)");
    pEXPECT(item = tbd_get_first(a));
    iEXPECT(check_payload(item->payload), 1);
    pEXPECT(tbd_delete_items(a, item, tbd_get_index(a, 1), 1));
    iEXPECT(tbd_get_len(a), ORIG_SIZE - 2);
    CHECK_DLIST(a, ORIG_SIZE - 2);

    HEADER("tbd_delete_items(NULL, NULL, 1)");
    pEXPECT(tbd_delete_items(a, NULL, NULL, 1));
    iEXPECT(tbd_get_len(a), ORIG_SIZE - 3);
    CHECK_DLIST(a, ORIG_SIZE - 3);

    HEADER("tbd_delete_items(NULL, NULL, -1)");
    pEXPECT(tbd_delete_items(a, NULL, NULL, -1));
    iEXPECT(tbd_get_len(a), ORIG_SIZE - 4);
    CHECK_DLIST(a, ORIG_SIZE - 4);

    HEADER("tbd_delete_items(NULL, NULL, 0)");
    pEXPECT(tbd_delete_items(a, NULL, NULL, 0));
    iEXPECT(tbd_get_len(a), 0);
    CHECK_DLIST(a, 0);

    free_dlist(&a, free_callback, create_callback, 
               copy_callback, &count);
end:
    return(errors);
}

int dlist_test_copy_items(int                   width, 
                          tbd_free_callback_t   free_callback, 
                          tbd_create_callback_t create_callback, 
                          tbd_copy_callback_t   copy_callback)
{
    int errors = 0;
    tbd a = NULL;
    tbd b = NULL;
    tbd_item item = NULL;
    int value;
    int count = 0;
    int user_data = 0;

    HEADER("tbd_copy_items(NULL, NULL, a, NULL, NULL, 1)");
    user_data = 0;
    pEXPECT(a = new_dlist(ORIG_SIZE, width, free_callback, create_callback, 
                          copy_callback, &user_data));
    pEXPECT(b = tbd_copy_items(NULL, NULL, a, NULL, NULL, 1));
    iEXPECT(tbd_get_len(a), ORIG_SIZE);
    iEXPECT(tbd_get_len(b), 1);
    pEXPECT(item = tbd_get_first(b));
    
    // no point testing this if there was no callback to set up the payload:
    if (copy_callback) {
        iEXPECT(check_payload(item->payload), 1); 
        cEXPECT(item, COPY_OFFSET + 0);
    }

    free_dlist(&a, free_callback, create_callback, 
               copy_callback, &count);

    free_dlist(&b, free_callback, create_callback, 
               copy_callback, &count);
    
    HEADER("tbd_copy_items(NULL, NULL, a, NULL, NULL, 0)");
    user_data = 0;
    pEXPECT(a = new_dlist(ORIG_SIZE, width, free_callback, create_callback, 
                          copy_callback, &user_data));
    pEXPECT(b = tbd_copy_items(NULL, NULL, a, NULL, NULL, 0));
    iEXPECT(tbd_get_len(b), ORIG_SIZE);
    pEXPECT(item = tbd_get_first(b));
    
    // no point testing this if there was no callback to set up the payload:
    if (copy_callback) {
        iEXPECT(check_payload(item->payload), 1); 
        iEXPECT(((test_payload)item->payload)->count, COPY_OFFSET + 0);
    }

    free_dlist(&a, free_callback, create_callback, 
               copy_callback, &count);

    free_dlist(&b, free_callback, create_callback, 
               copy_callback, &count);

    HEADER("tbd_copy_items(NULL, NULL, a, first, NULL, ORIG_SIZE / 2)");
    user_data = 0;
    pEXPECT(a = new_dlist(ORIG_SIZE, width, free_callback, create_callback, 
                          copy_callback, &user_data));
    pEXPECT(b = tbd_copy_items(NULL, NULL, a, tbd_get_first(a), NULL,
                               ORIG_SIZE / 2));
    iEXPECT(tbd_get_len(b), ORIG_SIZE / 2);
    pEXPECT(item = tbd_get_first(b));

    // no point testing this if there was no callback to set up the payload:
    if (copy_callback) {
        iEXPECT(check_payload(item->payload), 1); 
        cEXPECT(item, COPY_OFFSET + 0);
        pEXPECT(item = tbd_get_index(b, ORIG_SIZE / 2 - 1));
        iEXPECT(check_payload(item->payload), 1); 
        cEXPECT(item, COPY_OFFSET + ORIG_SIZE / 2 - 1);
    }

    free_dlist(&a, free_callback, create_callback, 
               copy_callback, &count);

    free_dlist(&b, free_callback, create_callback, 
               copy_callback, &count);

    HEADER("tbd_copy_items(NULL, NULL, a, first, NULL, ORIG_SIZE / 2)");
    user_data = 0;
    pEXPECT(a = new_dlist(ORIG_SIZE, width, free_callback, create_callback, 
                          copy_callback, &user_data));
    pEXPECT(b = tbd_copy_items(NULL, NULL, a, tbd_get_first(a), NULL,
                               ORIG_SIZE / 2));
    iEXPECT(tbd_get_len(b), ORIG_SIZE / 2);
    pEXPECT(item = tbd_get_first(b));
    pEXPECT(tbd_copy_items(b, NULL, a, tbd_get_first(a), NULL,
                           ORIG_SIZE / 2));
    iEXPECT(tbd_get_len(b), ORIG_SIZE);
    pEXPECT(item = tbd_get_first(b));

    // no point testing this if there was no callback to set up the payload:
    if (copy_callback) {
        iEXPECT(check_payload(item->payload), 1); 
        cEXPECT(item, COPY_OFFSET + 0);
        pEXPECT(item = tbd_get_index(b, ORIG_SIZE / 2 - 1));
        iEXPECT(check_payload(item->payload), 1); 
        cEXPECT(item, COPY_OFFSET + ORIG_SIZE / 2 - 1);
    }

    free_dlist(&a, free_callback, create_callback, 
               copy_callback, &count);

    free_dlist(&b, free_callback, create_callback, 
               copy_callback, &count);

    HEADER("tbd_copy_items(NULL, NULL, a, NULL, NULL, -ORIG_SIZE)");
    user_data = 0;
    pEXPECT(a = new_dlist(ORIG_SIZE, width, free_callback, create_callback, 
                          copy_callback, &user_data));
    pEXPECT(b = tbd_copy_items(NULL, NULL, a, NULL, NULL, -ORIG_SIZE));
    iEXPECT(tbd_get_len(b), ORIG_SIZE);
    pEXPECT(item = tbd_get_first(b));

    // no point testing this if there was no callback to set up the payload:
    if (copy_callback) {
        iEXPECT(check_payload(item->payload), 1); 
        cEXPECT(item, COPY_OFFSET + ORIG_SIZE - 1);
        pEXPECT(item = tbd_get_index(b, ORIG_SIZE - 1));
        iEXPECT(check_payload(item->payload), 1); 
        cEXPECT(item, COPY_OFFSET + 0);
    }

    free_dlist(&a, free_callback, create_callback, 
               copy_callback, &count);

    free_dlist(&b, free_callback, create_callback, 
               copy_callback, &count);

    HEADER("tbd_copy_items(b, ORIG_SIZE / 2, a, ORIG_SIZE / 2, NULL, -(ORIG_SIZE / 2))");
    user_data = 0;
    pEXPECT(a = new_dlist(ORIG_SIZE, width, free_callback, create_callback, 
                          copy_callback, &user_data));
    pEXPECT(b = new_dlist(ORIG_SIZE, width, free_callback, create_callback, 
                          copy_callback, &user_data));
    pEXPECT(tbd_copy_items(b, tbd_get_index(b, ORIG_SIZE / 2 - 1),
                           a, tbd_get_index(a, ORIG_SIZE / 2 - 1),
                           NULL, -ORIG_SIZE));
    iEXPECT(tbd_get_len(b), ORIG_SIZE + ORIG_SIZE / 2);
    pEXPECT(item = tbd_get_first(b));

    // no point testing this if there was no callback to set up the payload:
    if (copy_callback) {
        iEXPECT(check_payload(item->payload), 1); 
        cEXPECT(item, ORIG_SIZE);
        pEXPECT(item = tbd_get_index(b, ORIG_SIZE / 2)); // the first of the copied items
        iEXPECT(check_payload(item->payload), 1); 
        cEXPECT(item, COPY_OFFSET + ORIG_SIZE / 2 - 1);
    }

    free_dlist(&a, free_callback, create_callback, 
               copy_callback, &count);

    free_dlist(&b, free_callback, create_callback, 
               copy_callback, &count);

end:
    return(errors);
}

int dlist_test_move_items(int                   width, 
                          tbd_free_callback_t   free_callback, 
                          tbd_create_callback_t create_callback, 
                          tbd_copy_callback_t   copy_callback)
{
    int errors = 0;
    tbd a = NULL;
    tbd b = NULL;
    tbd_item item = NULL;
    int value;
    int count = 0;
    int user_data = 0;

    HEADER("tbd_move_items(NULL, NULL, a, NULL, NULL, 1)");
    user_data = 0;
    pEXPECT(a = new_dlist(ORIG_SIZE, width, free_callback, create_callback, 
                          copy_callback, &user_data));
    pEXPECT(b = tbd_move_items(NULL, NULL, a, NULL, NULL,1));
    iEXPECT(tbd_get_len(a), ORIG_SIZE - 1);
    iEXPECT(tbd_get_len(b), 1);
    pEXPECT(item = tbd_get_first(b));
    if (copy_callback) {
        cEXPECT(item, 0);
        npEXPECT(item = tbd_get_next(item));
    }

    free_dlist(&a, free_callback, create_callback, 
               copy_callback, &count);

    free_dlist(&b, free_callback, create_callback, 
               copy_callback, &count);

    HEADER("tbd_move_items(b, NULL, a, NULL, NULL, 1)");
    user_data = 0;
    pEXPECT(a = new_dlist(ORIG_SIZE, width, free_callback, create_callback, 
                          copy_callback, &user_data));
    pEXPECT(b = new_dlist(ORIG_SIZE, width, free_callback, create_callback, 
                          copy_callback, &user_data));
    pEXPECT(tbd_move_items(b, NULL, a, NULL, NULL, 1));
    iEXPECT(tbd_get_len(a), ORIG_SIZE - 1);
    iEXPECT(tbd_get_len(b), ORIG_SIZE + 1);
    pEXPECT(item = tbd_get_first(b));
    if (copy_callback) {
        cEXPECT(item, 0);
        pEXPECT(item = tbd_get_next(item));
        cEXPECT(item, ORIG_SIZE + 0);
        pEXPECT(item = tbd_get_next(item));
        cEXPECT(item, ORIG_SIZE + 1);
        pEXPECT(item = tbd_get_next(item));
        cEXPECT(item, ORIG_SIZE + 2);
    }

    free_dlist(&a, free_callback, create_callback, 
               copy_callback, &count);

    free_dlist(&b, free_callback, create_callback, 
               copy_callback, &count);

    HEADER("tbd_move_items(b, NULL, a, NULL, NULL, -1)");
    user_data = 0;
    pEXPECT(a = new_dlist(ORIG_SIZE, width, free_callback, create_callback, 
                          copy_callback, &user_data));
    pEXPECT(b = new_dlist(ORIG_SIZE, width, free_callback, create_callback, 
                          copy_callback, &user_data));
    pEXPECT(tbd_move_items(b, NULL, a, NULL, NULL, -1));
    iEXPECT(tbd_get_len(a), ORIG_SIZE - 1);
    iEXPECT(tbd_get_len(b), ORIG_SIZE + 1);
    pEXPECT(item = tbd_get_first(b));
    if (copy_callback) {
        cEXPECT(item, ORIG_SIZE - 1);
        pEXPECT(item = tbd_get_next(item));
        cEXPECT(item, ORIG_SIZE + 0);
        pEXPECT(item = tbd_get_next(item));
        cEXPECT(item, ORIG_SIZE + 1);
        pEXPECT(item = tbd_get_next(item));
        cEXPECT(item, ORIG_SIZE + 2);
    }

    free_dlist(&a, free_callback, create_callback, 
               copy_callback, &count);

    free_dlist(&b, free_callback, create_callback, 
               copy_callback, &count);

    HEADER("tbd_move_items(b, NULL, a, NULL, NULL, 0)");
    user_data = 0;
    pEXPECT(a = new_dlist(ORIG_SIZE, width, free_callback, create_callback, 
                          copy_callback, &user_data));
    pEXPECT(b = new_dlist(ORIG_SIZE, width, free_callback, create_callback, 
                          copy_callback, &user_data));
    pEXPECT(tbd_move_items(b, NULL, a, NULL, NULL, 0));
    iEXPECT(tbd_get_len(a), 0);
    iEXPECT(tbd_get_len(b), ORIG_SIZE * 2);
    pEXPECT(item = tbd_get_first(b));
    if (copy_callback) {
        cEXPECT(item, 0);
        pEXPECT(item = tbd_get_next(item));
        cEXPECT(item, 1);
        pEXPECT(item = tbd_get_next(item));
        cEXPECT(item, 2);
        pEXPECT(item = tbd_get_next(item));
        cEXPECT(item, 3);
    }

    free_dlist(&a, free_callback, create_callback, 
               copy_callback, &count);

    free_dlist(&b, free_callback, create_callback, 
               copy_callback, &count);

    HEADER("tbd_move_items(b, ORIG_SIZE/2, a, ORIG_SIZE/2, 0)");
    user_data = 0;
    pEXPECT(a = new_dlist(ORIG_SIZE, width, free_callback, create_callback, 
                          copy_callback, &user_data));
    pEXPECT(b = new_dlist(ORIG_SIZE, width, free_callback, create_callback, 
                          copy_callback, &user_data));
    pEXPECT(tbd_move_items(b, tbd_get_index(b, ORIG_SIZE / 2),
                           a, tbd_get_index(a, ORIG_SIZE / 2), 
                           NULL, 0));
    iEXPECT(tbd_get_len(a), ORIG_SIZE / 2);
    iEXPECT(tbd_get_len(b), ORIG_SIZE + ORIG_SIZE / 2);
    pEXPECT(item = tbd_get_first(b));
    if (copy_callback) {
        cEXPECT(item, ORIG_SIZE);
        pEXPECT(item = tbd_get_index(b, ORIG_SIZE / 2));
        cEXPECT(item, ORIG_SIZE + ORIG_SIZE / 2);
        pEXPECT(item = tbd_get_next(item));
        cEXPECT(item, ORIG_SIZE / 2);
        pEXPECT(item = tbd_get_next(item));
        cEXPECT(item, ORIG_SIZE / 2 + 1);
    }

    free_dlist(&a, free_callback, create_callback, 
               copy_callback, &count);

    free_dlist(&b, free_callback, create_callback, 
               copy_callback, &count);

    HEADER("tbd_move_items(b, NULL, a, ORIG_SIZE/2, -ORIG_SIZE)");
    user_data = 0;
    pEXPECT(a = new_dlist(ORIG_SIZE, width, free_callback, create_callback, 
                          copy_callback, &user_data));
    pEXPECT(b = new_dlist(ORIG_SIZE, width, free_callback, create_callback, 
                          copy_callback, &user_data));
    pEXPECT(tbd_move_items(b, NULL,
                           a, tbd_get_index(a, ORIG_SIZE / 2),
                           NULL, -ORIG_SIZE));
    iEXPECT(tbd_get_len(a), ORIG_SIZE / 2 - 1);
    iEXPECT(tbd_get_len(b), ORIG_SIZE + ORIG_SIZE / 2 + 1);
    pEXPECT(item = tbd_get_first(b));
    if (copy_callback) {
        cEXPECT(item, ORIG_SIZE / 2);
        pEXPECT(item = tbd_get_next(item));
        cEXPECT(item, ORIG_SIZE / 2 - 1);
        pEXPECT(item = tbd_get_next(item));
        cEXPECT(item, ORIG_SIZE / 2 - 2);
        pEXPECT(item = tbd_get_index(b, ORIG_SIZE / 2));
        cEXPECT(item, 0);
        pEXPECT(item = tbd_get_index(b, ORIG_SIZE / 2 + 1));
        cEXPECT(item, ORIG_SIZE);
    }

    free_dlist(&a, free_callback, create_callback, 
               copy_callback, &count);

    free_dlist(&b, free_callback, create_callback, 
               copy_callback, &count);

#if 0
    // no point testing this if there was no callback to set up the payload:
    if (copy_callback) {
        iEXPECT(check_payload(item->payload), 1); 
        iEXPECT(((test_payload)item->payload)->count, COPY_OFFSET + 0);
    }

    free_dlist(&a, free_callback, create_callback, 
               copy_callback, &count);

    free_dlist(&b, free_callback, create_callback, 
               copy_callback, &count);
    
    HEADER("tbd_move_items(NULL, NULL, a, NULL, NULL, 0)");
    user_data = 0;
    pEXPECT(a = new_dlist(ORIG_SIZE, width, free_callback, create_callback, 
                          copy_callback, &user_data));
    pEXPECT(b = tbd_move_items(NULL, NULL, a, NULL, NULL, 0));
    iEXPECT(tbd_get_len(b), ORIG_SIZE);
    pEXPECT(item = tbd_get_first(b));

    // no point testing this if there was no callback to set up the payload:
    if (copy_callback) {
        iEXPECT(check_payload(item->payload), 1); 
        iEXPECT(((test_payload)item->payload)->count, COPY_OFFSET + 0);
    }

    free_dlist(&a, free_callback, create_callback, 
               copy_callback, &count);

    free_dlist(&b, free_callback, create_callback, 
               copy_callback, &count);

    HEADER("tbd_move_items(NULL, NULL, a, first, NULL, 10)");
    user_data = 0;
    pEXPECT(a = new_dlist(ORIG_SIZE, width, free_callback, create_callback, 
                          copy_callback, &user_data));
    pEXPECT(b = tbd_move_items(NULL, NULL, a, tbd_get_first(a), 
                               NULL, 10));
    iEXPECT(tbd_get_len(b), 10);
    pEXPECT(item = tbd_get_first(b));

    // no point testing this if there was no callback to set up the payload:
    if (copy_callback) {
        iEXPECT(check_payload(item->payload), 1); 
        iEXPECT(((test_payload)item->payload)->count, MOVE_OFFSET + 0);
    }

    free_dlist(&a, free_callback, create_callback, 
               copy_callback, &count);

    free_dlist(&b, free_callback, create_callback, 
               copy_callback, &count);

    HEADER("tbd_move_items(b, NULL, a, first, NULL, 10)");
    user_data = 0;
    pEXPECT(a = new_dlist(ORIG_SIZE, width, free_callback, create_callback, 
                          copy_callback, &user_data));
    pEXPECT(b = tbd_move_items(NULL, NULL, a, tbd_get_first(a), NULL, 10));
    iEXPECT(tbd_get_len(b), 10);
    pEXPECT(item = tbd_get_first(b));
    pEXPECT(tbd_move_items(b, NULL, a, tbd_get_first(a), NULL, 10));
    iEXPECT(tbd_get_len(b), 20);
    pEXPECT(item = tbd_get_first(b));

    // no point testing this if there was no callback to set up the payload:
    if (copy_callback) {
        iEXPECT(check_payload(item->payload), 1); 
        iEXPECT(((test_payload)item->payload)->count, COPY_OFFSET + 0);
    }

    free_dlist(&a, free_callback, create_callback, 
               copy_callback, &count);

    free_dlist(&b, free_callback, create_callback, 
               copy_callback, &count);
#endif
    
end:
    return(errors);
}

int dlist_test_push_pop_items(int                   width, 
                              tbd_free_callback_t   free_callback, 
                              tbd_create_callback_t create_callback, 
                              tbd_copy_callback_t   copy_callback)
{
    int errors = 0;
    tbd a = NULL;
    // tbd b = NULL;
    tbd_item item = NULL;
    int value;
    int count = 0;
    int user_data = 0;
    void * payload;

    HEADER("tbd_push_pop_items()");
    HEADER("tbd_push(NULL)");
    user_data = 0;
    pEXPECT(tbd_push(&a, NULL));
 
    free_dlist(&a, free_callback, create_callback, 
               copy_callback, &count);
   
    HEADER("tbd_push(a); tba_pop(a)");
    pEXPECT(a = new_dlist(ORIG_SIZE, width, free_callback, create_callback, 
                          copy_callback, &user_data));
    pEXPECT(tbd_push(&a, NULL));
    iEXPECT(tbd_get_len(a), ORIG_SIZE + 1);
    pEXPECT(item = tbd_get_newest(a));
    npEXPECT(item->payload);
    npEXPECT(tbd_pop(a));
 
    free_dlist(&a, free_callback, create_callback, 
               copy_callback, &count);
   

    HEADER("tbd_push(a); tbd_pop_oldest(a)");
    pEXPECT(a = new_dlist(ORIG_SIZE, width, free_callback, create_callback,
                          copy_callback, &user_data));
    pEXPECT(tbd_push(&a, NULL));
    iEXPECT(tbd_get_len(a), ORIG_SIZE + 1);
    pEXPECT(payload = tbd_pop_oldest(a));
    iEXPECT(check_payload(payload), 1);
    xfree(payload);
    free_dlist(&a, free_callback, create_callback,
               copy_callback, &count);

end:
    return(errors);
}

int _compare_payloads(const void *p1_in, const void *p2_in)
{
    test_payload p1 = *(test_payload *)p1_in;
    test_payload p2 = *(test_payload *)p2_in;
    if (!check_payload(p1)) {
        printf("Bad payload\n");
        exit(1);
    }
    if (!check_payload(p2)) {
        printf("Bad payload\n");
        exit(1);
    }
    if (p2->count < p1->count) return (-1);
    if (p2->count > p1->count) return (1);
    return(0);
}
    
int dlist_test_sort_items(int                   width, 
                          tbd_free_callback_t   free_callback, 
                          tbd_create_callback_t create_callback, 
                          tbd_copy_callback_t   copy_callback)
{
    int errors = 0;
    tbd a = NULL;
    // tbd b = NULL;
    tbd_item item = NULL;
    int value;
    int count = 0;
    int user_data = 0;

    HEADER("tbd_sort()");
    pEXPECT(a = new_dlist(ORIG_SIZE, width, free_callback, create_callback, 
                          copy_callback, &user_data));
    pEXPECT(tbd_sort(a, NULL, 0, _compare_payloads));
    iEXPECT(tbd_get_len(a), ORIG_SIZE);
    pEXPECT(item = tbd_get_first(a));
    cEXPECT(item, ORIG_SIZE - 1);
    pEXPECT(item = tbd_get_last(a));
    cEXPECT(item, 0);
    free_dlist(&a, free_callback, create_callback, 
               copy_callback, &count);

    user_data = 0;
    HEADER("tbd_sort(5,10)");
    pEXPECT(a = new_dlist(ORIG_SIZE, width, free_callback, create_callback, 
                          copy_callback, &user_data));
    pEXPECT(tbd_sort(a, tbd_get_index(a, 5), 10, _compare_payloads));
    iEXPECT(tbd_get_len(a), ORIG_SIZE);
    pEXPECT(item = tbd_get_first(a));
    cEXPECT(item, 0);
    pEXPECT(item = tbd_get_index(a, 4));
    cEXPECT(item, 4);
    pEXPECT(item = tbd_get_index(a, 5));
    cEXPECT(item, 14);
    pEXPECT(item = tbd_get_index(a, 14));
    cEXPECT(item, 5);
    pEXPECT(item = tbd_get_index(a, 15));
    cEXPECT(item, 15);
    pEXPECT(item = tbd_get_last(a));
    cEXPECT(item, ORIG_SIZE - 1);
    free_dlist(&a, free_callback, create_callback, 
               copy_callback, &count);
    
end:
    return(errors);
}

int dlist_test(int                   width, 
               tbd_free_callback_t   free_callback, 
               tbd_create_callback_t create_callback, 
               tbd_copy_callback_t   copy_callback)
{
    int errors = 0;

    printf("width = %d callbacks = (%p,%p,%p)\n", width, free_callback,
           create_callback, copy_callback);

    CHECK_ERRORS(dlist_test_delete_items(width, free_callback, create_callback, copy_callback));
    CHECK_ERRORS(dlist_test_chop_items(width, free_callback, create_callback, copy_callback));
    CHECK_ERRORS(dlist_test_copy_items(width, free_callback, create_callback, copy_callback));
    CHECK_ERRORS(dlist_test_move_items(width, free_callback, create_callback, copy_callback));
    CHECK_ERRORS(dlist_test_push_pop_items(width, free_callback, create_callback, copy_callback));
    CHECK_ERRORS(dlist_test_sort_items(width, free_callback, create_callback, copy_callback));

end:
    return(errors);
}

int unittest()
{
    printf("\nRunning unittest\n");

    CHECK_ERRORS(dlist_test(ORIG_WIDTH, NULL, NULL, NULL));
    CHECK_ERRORS(dlist_test(ORIG_WIDTH, f_free_callback, 
                            f_create_callback, f_copy_callback));
    CHECK_ERRORS(dlist_test(0, NULL, NULL, NULL));
    CHECK_ERRORS(dlist_test(0, v_free_callback, v_create_callback, 
                            v_copy_callback));
end:
    return(errors);
}

int simple_test()
{
    typedef struct my_payload_s {
        int count;
        char description[50];
    } my_payload;
    
    my_payload t;
    my_payload *p = NULL;
    tbd_item item = NULL;
    int i;
    
    tbd d = tbd_new(sizeof(struct my_payload_s), NULL, NULL, NULL, NULL); 
    for (i = 0; i < ORIG_SIZE; i++) {
        t.count = i;
        snprintf(t.description, 50, "this is payload %d", i);
        item = tbd_append_new(&d, item); // malloc's space for the payload
        memcpy((void *)item->payload, (void *)&t, sizeof(struct my_payload_s));
    }
    item = tbd_get_index(d, ORIG_SIZE / 2);
    p = (my_payload *)item->payload;
    printf("%d: desc=%s\n", p->count, p->description); // gives:
    // 234: desc=this is payload 234
    tbd_free(&d); // all gone
    return(0);
}

int tbd_check()
{
    tbd                   a, b;
    int                   width = 20;
    tbd_free_callback_t   free_callback = NULL;
    tbd_create_callback_t create_callback = NULL;
    tbd_copy_callback_t   copy_callback = NULL;
    void *                callback_data = NULL;
    
    header("tbd_check", __LINE__);
    pEXPECT(a = tbd_new(width, free_callback, create_callback, 
                        copy_callback, callback_data));
    a->sentinel1 = 0;
    npEXPECT(b = tbd_copy_items(NULL, NULL, a, NULL, NULL, 0));
    tbd_free(&a); // will fail so:
    free(a); a = NULL;
    tbd_free(&b); // will fail so:
    free(b); b = NULL;

    pEXPECT(a = tbd_new(width, free_callback, create_callback, 
                        copy_callback, callback_data));
    a->sentinel1 = 0;
    npEXPECT(b = tbd_copy_items(NULL, NULL, a, NULL, NULL, 0));
    tbd_free(&a); // will fail so:
    free(a); a = NULL;
    tbd_free(&b); // will fail so:
    free(b); b = NULL;

end:
    return(errors);
}

int main(int argc, char **argv)
{
    int errors = 0;

    setenv("TBDRRAY_CHECK", "1", 2);

    CHECK_ERRORS(simple_test());
    CHECK_ERRORS(unittest());
    CHECK_ERRORS(tbd_check());

end:                 
    printf("\nTotal: %d error%c\n", errors, errors == 1? ' ': 's');
    return(errors);
}

